// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86;

public enum Action
{
    POP = 0x00,
    PUSH = 0x01,
    KILL = 0x02,
    LIVE = 0x03,
    DEAD = 0x04
}

public abstract class BaseGcTransition
{
    public int CodeOffset { get; set; }

    public BaseGcTransition() { }

    public BaseGcTransition(int codeOffset)
    {
        CodeOffset = codeOffset;
    }
}

public class CalleeSavedRegister : BaseGcTransition
{
    public RegMask Register { get; set; }

    public CalleeSavedRegister() { }

    public CalleeSavedRegister(int codeOffset, RegMask reg)
        : base(codeOffset)
    {
        Register = reg;
    }

    public override string ToString()
    {
        return $"thisptr in {Register}";
    }
}

public class IPtrMask : BaseGcTransition
{
    public uint IMask { get; set; }

    public IPtrMask() { }

    public IPtrMask(int codeOffset, uint imask)
        : base(codeOffset)
    {
        IMask = imask;
    }

    public override string ToString()
    {
        return $"iptrMask: {IMask}";
    }
}

public class GcTransitionRegister : BaseGcTransition
{
    public RegMask Register { get; set; }
    public Action IsLive { get; set; }
    public int PushCountOrPopSize { get; set; }
    public bool IsThis { get; set; }
    public bool Iptr { get; set; }

    public GcTransitionRegister() { }

    public GcTransitionRegister(int codeOffset, RegMask reg, Action isLive, bool isThis = false, bool iptr = false, int pushCountOrPopSize = 1)
        : base(codeOffset)
    {
        Register = reg;
        IsLive = isLive;
        PushCountOrPopSize = pushCountOrPopSize;
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();

        if (IsLive == Action.LIVE)
        {
            sb.Append($"reg {Register} becoming live");
        }
        else if (IsLive == Action.DEAD)
        {
            sb.Append($"reg {Register} becoming dead");
        }
        else
        {
            sb.Append((IsLive == Action.PUSH ? "push" : "pop") + $" {Register}");
            if (PushCountOrPopSize != 1)
                sb.Append($" {PushCountOrPopSize}");
        }

        if (IsThis)
            sb.Append(" 'this'");
        if (Iptr)
            sb.Append(" (iptr)");

        return sb.ToString();
    }
}

public class GcTransitionPointer : BaseGcTransition
{
    private bool _isEbpFrame;
    public uint ArgOffset { get; set; }
    public uint ArgCount { get; set; }
    public Action Act { get; set; }
    public bool IsPtr { get; set; }
    public bool IsThis { get; set; }
    public bool Iptr { get; set; }

    public GcTransitionPointer() { }

    public GcTransitionPointer(int codeOffset, uint argOffs, uint argCnt, Action act, bool isEbpFrame, bool isThis = false, bool iptr = false, bool isPtr = true)
        : base(codeOffset)
    {
        _isEbpFrame = isEbpFrame;
        CodeOffset = codeOffset;
        ArgOffset = argOffs;
        ArgCount = argCnt;
        Act = act;
        IsPtr = isPtr;
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();

        if (Act == Action.KILL)
        {
            sb.Append($"kill args {ArgOffset}");
        }
        else
        {
            if (Act == Action.POP)
            {
                sb.Append($"pop ");
            }
            else
            {
                sb.Append($"push ");
            }
            if (IsPtr)
            {
                sb.Append($"{ArgOffset}");
                if (!_isEbpFrame)
                {
                    sb.Append($" args ({ArgCount})");
                }
                else if (Act == Action.POP)
                {
                    sb.Append(" ptrs");
                }

                if (IsThis)
                    sb.Append(" 'this'");
                if (Iptr)
                    sb.Append(" (iptr)");
            }
            else
            {
                sb.Append("non-pointer");
                sb.Append($" ({ArgCount})");
            }
        }

        return sb.ToString();
    }
}

public class GcTransitionCall : BaseGcTransition
{
    public struct CallRegister
    {
        public RegMask Register { get; set; }
        public bool IsByRef { get; set; }

        public CallRegister(RegMask reg, bool isByRef)
        {
            Register = reg;
            IsByRef = isByRef;
        }
    }

    public struct PtrArg
    {
        public uint StackOffset { get; set; }
        public uint LowBit { get; set; }

        public PtrArg(uint stackOffset, uint lowBit)
        {
            StackOffset = stackOffset;
            LowBit = lowBit;
        }
    }

    public List<CallRegister> CallRegisters { get; set; }
    public List<PtrArg> PtrArgs { get; set; }
    public uint ArgMask { get; set; }
    public uint IArgs { get; set; }

    public GcTransitionCall(int codeOffset)
        : base(codeOffset)
    {
        CallRegisters = new List<CallRegister>();
        PtrArgs = new List<PtrArg>();
        ArgMask = 0;
        IArgs = 0;
    }

    public GcTransitionCall(int codeOffset, bool isEbpFrame, uint regMask, uint byRefRegMask)
        : base(codeOffset)
    {
        CallRegisters = new List<CallRegister>();
        PtrArgs = new List<PtrArg>();
        if ((regMask & 1) != 0)
        {
            RegMask reg = RegMask.EDI;
            bool isByRef = (byRefRegMask & 1) != 0;
            CallRegisters.Add(new CallRegister(reg, isByRef));
        }
        if ((regMask & 2) != 0)
        {
            RegMask reg = RegMask.ESI;
            bool isByRef = (byRefRegMask & 2) != 0;
            CallRegisters.Add(new CallRegister(reg, isByRef));
        }
        if ((regMask & 4) != 0)
        {
            RegMask reg = RegMask.EBX;
            bool isByRef = (byRefRegMask & 4) != 0;
            CallRegisters.Add(new CallRegister(reg, isByRef));
        }
        if (!isEbpFrame)
        {
            if ((regMask & 8) != 0)
            {
                RegMask reg = RegMask.EBP;
                CallRegisters.Add(new CallRegister(reg, false));
            }
        }
        ArgMask = 0;
        IArgs = 0;
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();

        sb.Append("call [ ");
        foreach (CallRegister reg in CallRegisters)
        {
            sb.Append($"{reg.Register}");
            if (reg.IsByRef)
                sb.Append("(byref)");
            sb.Append(' ');
        }

        if (PtrArgs.Count > 0)
        {
            sb.Append(" ] ptrArgs=[ ");
            foreach (PtrArg ptrArg in PtrArgs)
            {
                sb.Append($"{ptrArg.StackOffset}");
                if (ptrArg.LowBit != 0)
                    sb.Append('i');
                sb.Append(' ');
            }
            sb.Append(" ]");
        }
        else
        {
            sb.Append($" ] argMask={ArgMask}");
            if (IArgs != 0)
                sb.Append($" (iargs={IArgs})");
        }

        return sb.ToString();
    }
}

public class StackDepthTransition : BaseGcTransition
{
    public int StackDepthChange { get; set; }

    public StackDepthTransition(int codeOffset)
        : base(codeOffset)
    {
    }

    public StackDepthTransition(int codeOffset, int stackDepthChange)
        : base(codeOffset)
    {
        StackDepthChange = stackDepthChange;
    }

    public override string ToString()
    {
        return $"stack depth delta: {StackDepthChange}";
    }
}
